
// <copyright file="XmlRichTextBox.cs" company="Subhadra Software Solutions">
//   
// </copyright>
// <summary>
//   Class XmlRichTextBox.
// </summary>

namespace SubhadraSolutions.Sharp.Utils.Windows.Forms
{
    using System;
    using System.Diagnostics;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Xml;
    using System.Xml.Linq;

    /// <summary>
    ///     Class XmlRichTextBox.
    /// </summary>
    public class XmlRichTextBox : RichTextBox
    {
        #region Fields

        /// <summary>
        ///     The _settings
        /// </summary>
        private XmlRichTextBoxSettings _settings;

        #endregion Fields

        #region Public Properties

        /// <summary>
        ///     The format settings.
        /// </summary>
        /// <value>The settings.</value>
        public XmlRichTextBoxSettings Settings
        {
            get
            {
                return this._settings ?? (this._settings = new XmlRichTextBoxSettings { AttributeKey = Color.Red, AttributeValue = Color.Blue, Tag = Color.Blue, Element = Color.DarkRed, Value = Color.Black, });
            }

            set
            {
                this._settings = value;
            }
        }

        #endregion Public Properties

        #region Public Methods and Operators

        /// <summary>
        /// Convert the Xml to Rtf with some formats specified in the XmlRichTextBoxSettings,
        ///     and then set the Rtf property to the value.
        /// </summary>
        /// <param name="includeDeclaration">
        /// Specify whether include the declaration.
        /// </param>
        public void Process(bool includeDeclaration)
        {
            try
            {
                // The Rtf contains 2 parts, header and content. The colortbl is a part of
                // the header, and the {1} will be replaced with the content.
                const string rtfFormat = @"{{\rtf1\ansi\ansicpg1252\deff0\deflang1033\deflangfe2052
            {{\fonttbl{{\f0\fnil Courier New;}}}}
            {{\colortbl ;{0}}}
            \viewkind4\uc1\pard\lang1033\f0
            {1}}}";

                // Get the XDocument from the Text property.
                XDocument xmlDoc = XDocument.Parse(this.Text, LoadOptions.None);

                var xmlRtfContent = new StringBuilder();

                // If includeDeclaration is true and the XDocument has declaration,
                // then add the declaration to the content.
                if (includeDeclaration && xmlDoc.Declaration != null)
                {
                    // The constants in XmlRichTextBoxSettings are used to specify the order
                    // in colortbl of the Rtf.
                    xmlRtfContent.AppendFormat(@"
            \cf{0} <?\cf{1} xml \cf{2} version\cf{0} =\cf0 ""\cf{3} {4}\cf0 ""
            \cf{2} encoding\cf{0} =\cf0 ""\cf{3} {5}\cf0 ""\cf{0} ?>\par", XmlRichTextBoxSettings.TagId, XmlRichTextBoxSettings.ElementId, XmlRichTextBoxSettings.AttributeKeyId, XmlRichTextBoxSettings.AttributeValueId, xmlDoc.Declaration.Version, xmlDoc.Declaration.Encoding);
                }

                // Get the Rtf of the root element.
                string rootRtfContent = this.ProcessElement(xmlDoc.Root, 0);

                xmlRtfContent.Append(rootRtfContent);

                // Construct the completed Rtf, and set the Rtf property to this value.
                this.Rtf = string.Format(rtfFormat, this.Settings.ToRtfFormatString(), xmlRtfContent);
            }
            catch (XmlException xmlException)
            {
                Debug.WriteLine(xmlException);

                // throw new ApplicationException("Please check the input Xml. Error:" + xmlException.Message,xmlException);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);

                // throw;
            }
        }

        /// <summary>
        /// Sets the text and process.
        /// </summary>
        /// <param name="xml">
        /// The XML.
        /// </param>
        /// <param name="includeDeclaration">
        /// if set to <c>true</c> [include declaration].
        /// </param>
        public void SetTextAndProcess(string xml, bool includeDeclaration = false)
        {
            this.Text = xml;
            this.Process(includeDeclaration);
        }

        #endregion Public Methods and Operators

        // Get the Rtf of the xml element.
        #region Methods

        /// <summary>
        /// Processes the element.
        /// </summary>
        /// <param name="element">
        /// The element.
        /// </param>
        /// <param name="level">
        /// The level.
        /// </param>
        /// <returns>
        /// System.String.
        /// </returns>
        /// <exception cref="System.ApplicationException">
        /// This viewer does not support the Xml file that has Namespace.
        /// </exception>
        private string ProcessElement(XElement element, int level)
        {
            // This viewer does not support the Xml file that has Namespace.
            if (!string.IsNullOrEmpty(element.Name.Namespace.NamespaceName))
            {
                throw new ApplicationException("This viewer does not support the Xml file that has Namespace.");
            }

            string elementRtfFormat;
            var childElementsRtfContent = new StringBuilder();
            var attributesRtfContent = new StringBuilder();

            // Construct the indent.
            var indent = new string(' ', 4 * level);

            // If the element has child elements or value, then add the element to the
            // Rtf. {{0}} will be replaced with the attributes and {{1}} will be replaced
            // with the child elements or value.
            if (element.HasElements || !string.IsNullOrWhiteSpace(element.Value))
            {
                elementRtfFormat = string.Format(@"
            {0}\cf{1} <\cf{2} {3}{{0}}\cf{1} >\par
            {{1}}
            {0}\cf{1} </\cf{2} {3}\cf{1} >\par", indent, XmlRichTextBoxSettings.TagId, XmlRichTextBoxSettings.ElementId, element.Name);

                // Construct the Rtf of child elements.
                if (element.HasElements)
                {
                    foreach (XElement childElement in element.Elements())
                    {
                        string childElementRtfContent = this.ProcessElement(childElement, level + 1);
                        childElementsRtfContent.Append(childElementRtfContent);
                    }
                }

                    // If !string.IsNullOrWhiteSpace(element.Value), then construct the Rtf
                // of the value.
                else
                {
                    childElementsRtfContent.AppendFormat(@"{0}\cf{1} {2}\par", new string(' ', 4 * (level + 1)), XmlRichTextBoxSettings.ValueId, CharacterEncoder.Encode(element.Value.Trim()));
                }
            }

                // This element only has attributes. {{0}} will be replaced with the attributes.
            else
            {
                elementRtfFormat = string.Format(@"
            {0}\cf{1} <\cf{2} {3}{{0}}\cf{1} />\par", indent, XmlRichTextBoxSettings.TagId, XmlRichTextBoxSettings.ElementId, element.Name);
            }

            // Construct the Rtf of the attributes.
            if (element.HasAttributes)
            {
                foreach (XAttribute attribute in element.Attributes())
                {
                    string attributeRtfContent = string.Format(@" \cf{0} {3}\cf{1} =\cf0 ""\cf{2} {4}\cf0 """, XmlRichTextBoxSettings.AttributeKeyId, XmlRichTextBoxSettings.TagId, XmlRichTextBoxSettings.AttributeValueId, attribute.Name, CharacterEncoder.Encode(attribute.Value));
                    attributesRtfContent.Append(attributeRtfContent);
                }

                attributesRtfContent.Append(" ");
            }

            return string.Format(elementRtfFormat, attributesRtfContent, childElementsRtfContent);
        }

        #endregion Methods
    }
}